package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.mapper.ApArticleConfigMapper;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ApArticleService;
import com.heima.article.service.ArticleFreemarkerService;
import com.heima.common.constants.ArticleConstants;
import com.heima.common.constants.BehaviorConstants;
import com.heima.common.constants.HotArticleConstants;
import com.heima.common.redis.CacheService;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.dtos.ArticleInfoDto;
import com.heima.model.article.mess.ArticleVisitStreamMess;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleConfig;
import com.heima.model.article.pojos.ApArticleContent;
import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.mess.UpdateArticleMess;
import com.heima.model.user.pojos.ApUser;
import com.heima.model.wemedia.dtos.StatisticsDto;
import com.heima.utils.common.DateUtils;
import com.heima.utils.thread.ApUserThreadLocalUtil;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {
    @Autowired
    private ApArticleMapper apArticleMapper;
    @Autowired
    private ApArticleConfigMapper apArticleConfigMapper;
    @Autowired
    private ApArticleContentMapper apArticleContentMapper;
    @Autowired
    private ArticleFreemarkerService articleFreemarkerService; // 基于freemarker 生成静态html文件的工具类

    @Autowired
    private CacheService cacheService;

    /**
     * 根据参数加载文章列表
     *
     * @param dto
     * @param type
     * @return
     */
    @Override
    public ResponseResult load(ArticleHomeDto dto, Short type) {
        //1.参数校验
        if (dto == null && type == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        if (dto.getSize() == null || dto.getSize() == 0) {
            //设置size 缺省值
            dto.setSize(10);
        }
        //分页最大值为50
        dto.setSize(Math.min(ArticleConstants.MAX_PAGE_SIZE, dto.getSize()));

        dto.setMinBehotTime(dto.getMinBehotTime() == null ? dto.getMinBehotTime() : new Date());
        dto.setMaxBehotTime(dto.getMaxBehotTime() == null ? dto.getMaxBehotTime() : new Date());

        //校验频道
        if (StringUtils.isEmpty(dto.getTag())) {
            dto.setTag(ArticleConstants.DEFAULT_TAG);
        }
        //2.数据处理
        //3.业务操作
        //调用mapper查询数据
        List<ApArticle> articleList = apArticleMapper.loadArticleList(dto, type);
        //4.返回结果
        return ResponseResult.okResult(articleList);
    }

    /**
     * 加载首页
     * 优先读取缓存中的数据,如果缓存中没有再读取数据库中的数据
     *
     * @param dto
     * @param type
     * @return
     */
    @Override
    public ResponseResult loadFirstPage(ArticleHomeDto dto, Short type) {
        //1.入参校验
        if (dto == null || dto.getTag() == null || type == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //2.根据dto中的频道数据,查询对应的redis中的缓存数据.如果缓存中没有数据,则查询数据库数据进行返回
        String articleVoStr = cacheService.get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + dto.getTag());
        if (StringUtils.isNotBlank(articleVoStr)) {
            List<HotArticleVo> articleVoList = JSON.parseObject(articleVoStr, List.class);
            return ResponseResult.okResult(articleVoList);
        } else {//缓存中没有对应的频道数据,则读取数据库

            return load(dto, type);
        }
    }

    /**
     * 保存或修改app端文章
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult saveArticle(ArticleDto dto) {
        //1.入参校验
        if (dto == null || StringUtils.isBlank(dto.getContent())) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //2.组装参数(数据处理)
        ApArticle apArticle = new ApArticle();
        //属性拷贝
        BeanUtils.copyProperties(dto, apArticle);
        //3.根据参数中是否有id判断是修改文章还是保存文章
        if (dto.getId() == null) {
            //3.1如果是保存文章,向 ap_article  ap_article_config ap_article_content三张表中插入数据
            //保存文章
            save(apArticle);

            //保存文章配置表
            ApArticleConfig apArticleConfig = new ApArticleConfig(apArticle.getId());
            apArticleConfigMapper.insert(apArticleConfig);

            //保存文章内容
            ApArticleContent apArticleContent = new ApArticleContent();
            apArticleContent.setArticleId(apArticle.getId());
            apArticleContent.setContent(dto.getContent());
            apArticleContentMapper.insert(apArticleContent);

        } else {
            //3.2如果是修改文章,向 ap_article ap_article_content两张表中插入数据
            updateById(apArticle);

            //更新文章内容表
            ApArticleContent apArticleContent =
                    apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery()
                            .eq(ApArticleContent::getArticleId, dto.getId()));
            apArticleContent.setContent(dto.getContent());
            apArticleContentMapper.updateById(apArticleContent);
        }

        ////结合freemarker模板引擎,生成文章详情静态html,上传到minio中,同时将返回的url回填到ap_article表中
        //异步构建静态页
        articleFreemarkerService.buildStaticArticleToMinIO(apArticle, dto.getContent());

        //返回文章的id
        return ResponseResult.okResult(apArticle.getId());
    }


    /**
     * 更新文章表用户行为相关的数据
     *
     * @param map
     * @return
     */
    @Override
    public void updateArticleByMap(Map map) {
        //1.入参校验
        if (map == null || map.get("articleId") == null) {
            return;
        }
        //2.根据map中的参数来更新ap_article表中的数据
        long articleId = (long) map.get("articleId");
        //根据文章id查询数据库中文章数据
        ApArticle apArticle = getById(articleId);
        //null参数检查初始化0
        if (apArticle.getLikes() == null) {
            apArticle.setLikes(0);
        }
        if (apArticle.getViews() == null) {
            apArticle.setViews(0);
        }
        //根据kafka中消息中的类型做相应的数据库表更新操作
        if (map.get("type").equals("likes")) {
            //点赞 +1
            apArticle.setLikes(apArticle.getLikes() + 1);

        } else if (map.get("type").equals("cancel_likes")) {
            //取消点赞 -1
            apArticle.setLikes(apArticle.getLikes() - 1);
        } else if (map.get("type").equals("read")) {
            //累加阅读数
            apArticle.setViews(apArticle.getViews() + Integer.valueOf(map.get("count") + ""));
        }
        //更新ap_article中相关的用户行文数据
        updateById(apArticle);
    }


    /**
     * 加载文章行为-数据回显
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult handlerLoadArticleBehavior(ArticleInfoDto dto) {
        //1.入参校验
        if (dto == null || dto.getArticleId() == null || dto.getAuthorId() == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //2.判断用户是否登录
        ApUser apUser = ApUserThreadLocalUtil.getUser();
        if (apUser == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
        }

        boolean islike = false, isunlike = false, iscollection = false, isfollow = false;
        //3.查询redis中存储的用户相关的数据
        //3.1 点赞
        String likesBehavior = (String) cacheService.hGet(BehaviorConstants.LIKES_BEHAVIOR + dto.getArticleId().toString(),
                apUser.getId().toString());
        if (StringUtils.isNotBlank(likesBehavior)) {
            //说明该用户对该篇文章进行了点赞
            islike = true;
        }

        //3.2 不喜欢
        String unlikeBehavior = (String) cacheService.hGet(BehaviorConstants.UNLIKES_BEHAVIOR + dto.getArticleId().toString(),
                apUser.getId().toString());
        if (StringUtils.isNotBlank(unlikeBehavior)) {
            //说明该用户不喜欢该文章
            isunlike = true;
        }

        //3.3 收藏 :登录用户收藏了哪些文章
        String collectionBehavior = (String) cacheService.hGet(BehaviorConstants.COLLECTION_BEHAVIOR + apUser.getId().toString(),
                dto.getArticleId().toString());
        if (StringUtils.isNotBlank(collectionBehavior)) {
            //说明当前登录用户收藏了该文章
            iscollection = true;
        }

        //3.4 关注  当前登录用户是否关注了该文章的作者
        Double zScore = cacheService.zScore(BehaviorConstants.USER_FOLLOW + apUser.getId().toString(),
                dto.getAuthorId().toString());
        if (zScore != null) {
            //说明当前登录用户关注了该文章的作者
            isfollow = true;
        }
//        "islike":false,
//                "isunlike":false,
//                "iscollection":false,
//                "isfollow":false
        //4.封装结果,返回
        Map map = new HashMap();
        map.put("islike", islike);
        map.put("isunlike", isunlike);
        map.put("iscollection", iscollection);
        map.put("isfollow", isfollow);

        return ResponseResult.okResult(map);
    }


    /**
     * 根据kafka中接收的用户行为数据,更新文章分值 , 更新缓存中的文章数据
     *
     * @param mess
     */
    @Override
    public void updateScore(ArticleVisitStreamMess mess) {
        //1.入参校验
        if (mess == null || mess.getArticleId() == null) {
            return;
        }
        //2.更新db中的文章点赞 收藏 评论 阅读的数据
        ApArticle dbApArticle = getById(mess.getArticleId());
        //计算文章原始分值
        Integer oldScore = computeScore(dbApArticle);

        //计算文章新获得的分值
        ApArticle newApArticle = new ApArticle();
        newApArticle.setLikes(mess.getLike());
        newApArticle.setCollection(mess.getCollect());
        newApArticle.setComment(mess.getComment());
        newApArticle.setViews(mess.getView());
        Integer newScore = computeScore(newApArticle);
        //3.计算文章的总分值
        Integer score = oldScore + (newScore * 3);

        //更新文章的行为数据
        ApArticle article = updateArticle(mess);

        //4.更新对应频道 redis中缓存的文章数据
        replaceDataToRedis(score, article, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + article.getChannelId());

        //5.更新推荐频道 redis中缓存的文章数据

        replaceDataToRedis(score, article, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);
    }

    /**
     * 替换redis中的热点文章数据, 并且将数据重新排序后更新到redis
     *
     * @param score
     * @param article
     */
    private void replaceDataToRedis(Integer score, ApArticle article, String key) {
        //根据key获取缓存中热点文章的数据
        String articleVoListStr = cacheService.get(key);
        if (StringUtils.isNotBlank(articleVoListStr)) {
            List<HotArticleVo> cacheHotArticleVoList = JSON.parseArray(articleVoListStr, HotArticleVo.class);//redis中缓存的数据

            boolean flag = true;
            //4.1如果缓存中存在该文章,则更新文章对应的分值,然后对redis中的文章数据重新排序后存入
            for (HotArticleVo hotArticleVo : cacheHotArticleVoList) {
                if (hotArticleVo.getId().equals(article.getId())) {
                    //说明当前缓存中存在该文章 ,更新文章分值
                    hotArticleVo.setScore(score);
                    flag = false;
                    break;
                }
            }
            if (flag) {//4.2缓存中不存在当前文章,则判断缓存中的文章数量是否超过30,
                if (cacheHotArticleVoList.size() > 30) {
                    cacheHotArticleVoList =
                            cacheHotArticleVoList.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed()).collect(Collectors.toList());

                    // a.超过30,比较当前文章与缓存中分值最低的文章的分值,如果大于,则替换后排序,再更新redis
                    HotArticleVo lastCacheHotArticle = cacheHotArticleVoList.get(cacheHotArticleVoList.size() - 1); //缓存中最后一个元素

                    if (lastCacheHotArticle.getScore() < score) {
                        cacheHotArticleVoList.remove(lastCacheHotArticle);
                        HotArticleVo hot = new HotArticleVo();
                        BeanUtils.copyProperties(article, hot);
                        hot.setScore(score);
                        cacheHotArticleVoList.add(hot);
                    }

                } else {
                    //b. redis中文章数据小于30,则直接存入,并且对redis的文章数据重新排序后更新redis
                    HotArticleVo hot = new HotArticleVo();
                    BeanUtils.copyProperties(article, hot);
                    hot.setScore(score);
                    cacheHotArticleVoList.add(hot);
                }
            }
            //将替换后的数据排序后重新存入缓存中
            cacheHotArticleVoList =
                    cacheHotArticleVoList.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed()).collect(Collectors.toList());
            cacheService.set(key, JSON.toJSONString(cacheHotArticleVoList));
        }
    }

    /**
     * 更新文章的行为数据
     *
     * @param mess
     * @return
     */
    private ApArticle updateArticle(ArticleVisitStreamMess mess) {
        ApArticle apArticle = getById(mess.getArticleId());
        apArticle.setLikes(apArticle.getLikes() == null ? 0 : apArticle.getLikes() + mess.getLike());
        apArticle.setViews(apArticle.getViews() == null ? 0 : apArticle.getViews() + mess.getView());
        apArticle.setComment(apArticle.getComment() == null ? 0 : apArticle.getComment() + mess.getComment());
        apArticle.setCollection(apArticle.getCollection() == null ? 0 : apArticle.getCollection() + mess.getCollect());

        updateById(apArticle);
        return apArticle;
    }


    /**
     * 计算每篇文章的分值
     *
     * @param apArticle
     * @return
     */
    private Integer computeScore(ApArticle apArticle) {
        Integer score = 0;
        if (apArticle.getLikes() != null) {
            score += apArticle.getLikes() * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }
        if (apArticle.getViews() != null) {
            score += apArticle.getViews();
        }
        if (apArticle.getComment() != null) {
            score += apArticle.getComment() * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }
        if (apArticle.getCollection() != null) {
            score += apArticle.getCollection() * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }
        return score;
    }

/*分页查询 图文列表*/
    @Override
    public PageResponseResult newPage(StatisticsDto dto) {
        //类型转化
        Date beginDate = DateUtils.stringToDate(dto.getBeginDate());
        Date endDate = DateUtils.stringToDate(dto.getEndDate());

        //检查参数
        dto.checkParam();

        //分页查询
        IPage page =new Page(dto.getPage(),dto.getSize());
        LambdaQueryWrapper<ApArticle> lambdaQueryWrapper =Wrappers.<ApArticle>lambdaQuery()
                .eq(ApArticle::getAuthorId,dto.getWmUserId())
                .between(ApArticle::getPublishTime,beginDate,endDate)
                .select(ApArticle::getId,ApArticle::getTitle,ApArticle::getLikes,
                        ApArticle::getCollection, ApArticle::getComment
                ,ApArticle::getViews);
        lambdaQueryWrapper.orderByDesc(ApArticle::getPublishTime);

        page=page(page,lambdaQueryWrapper);

        PageResponseResult responseResult = new PageResponseResult(dto.getPage(), dto.getSize(), (int) page.getTotal());
        responseResult.setData(page.getRecords());
        return responseResult;
    }

    @Override
    public ResponseResult queryLikesAndConllections(Integer wmUserId, Date beginDate, Date endDate) {

        Map map = apArticleMapper.queryLikesAndConllections(wmUserId, beginDate, endDate);
        return ResponseResult.okResult(map);
    }
}
